/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.agent.server.impl;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import org.eclipse.che.api.agent.server.AgentRegistry;
import org.eclipse.che.api.agent.server.exception.AgentException;
import org.eclipse.che.api.agent.shared.model.impl.AgentKeyImpl;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.api.agent.shared.model.AgentKey;
import org.eclipse.che.commons.annotation.Nullable;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * Sort agents respecting dependencies between them.
 *
 * @author Anatolii Bazko
 */
@Singleton
public class AgentSorter {

    private final AgentRegistry agentRegistry;

    @Inject
    public AgentSorter(AgentRegistry agentRegistry) {
        this.agentRegistry = agentRegistry;
    }

    /**
     * Sort agents respecting dependencies between them.
     * Handles circular dependencies.
     *
     * @see AgentKey
     * @see Agent#getDependencies()
     * @see AgentRegistry#getAgent(AgentKey)
     *
     * @param agentKeys list of agents to sort
     * @return list of created agents in proper order
     *
     * @throws AgentException
     *      if circular dependency found or agent creation failed or other unexpected error
     */
    public List<AgentKey> sort(@Nullable List<String> agentKeys) throws AgentException {
        List<AgentKey> sorted = new ArrayList<>();
        Set<String> pending = new HashSet<>();

        if (agentKeys != null) {
            for (String agentKey : agentKeys) {
                if (agentKey != null) {
                    doSort(AgentKeyImpl.parse(agentKey), sorted, pending);
                }
            }
        }

        return sorted;
    }

    private void doSort(AgentKey agentKey, List<AgentKey> sorted, Set<String> pending) throws AgentException {
        String agentId = agentKey.getId();

        Optional<AgentKey> alreadySorted = sorted.stream().filter(k -> k.getId().equals(agentId)).findFirst();
        if (alreadySorted.isPresent()) {
            return;
        }
        pending.add(agentId);

        Agent agent = agentRegistry.getAgent(agentKey);

        for (String dependency : agent.getDependencies()) {
            if (pending.contains(dependency)) {
                throw new AgentException(
                        String.format("Agents circular dependency found between '%s' and '%s'", dependency, agentId));
            }
            doSort(AgentKeyImpl.parse(dependency), sorted, pending);
        }

        sorted.add(agentKey);
        pending.remove(agentId);
    }
}
